import base64
from io import BytesIO
import zipfile

from openpyxl import load_workbook
from xlsxtpl.writerx import BookWriter
from num2words import num2words
from babel.dates import format_date
from jinja2.ext import LoopControlExtension
from odoo import _, api, fields, models
from odoo.tools.safe_eval import safe_eval, time
from odoo.exceptions import ValidationError

BookWriter.jinja_env_options = {
    'extensions': [LoopControlExtension]
}


class IrActionsReport(models.Model):
    _inherit = "ir.actions.report"

    report_type = fields.Selection(
        selection_add=[("xlsx-jinja", "XLSX Jinja")],
        ondelete={"xlsx-jinja": "cascade"},
    )
    jinja_id = fields.Many2one('xlsx.report.config')
    report_xlsx_jinja_template = fields.Binary(string="Report XLSX Jinja Template", related="jinja_id.report_xlsx_template")
    report_xlsx_jinja_template_name = fields.Char(string="Report XLSX Jinja Template Name")

    @api.constrains("report_type")
    def _check_report_type(self):
        for rec in self:
            if (
                    rec.report_type == "xlsx-jinja"
                    and not rec.report_xlsx_jinja_template
                    and not rec.report_xlsx_jinja_template_name.endswith(".xlsx")
            ):
                raise ValidationError(_("Please upload an XLSX Jinja template."))

    def _render_jinja_xlsx(self, report_ref, docids, data):
        report = self._get_report_from_name(report_ref)
        file_template = report.report_xlsx_jinja_template

        if not file_template:
            raise ValueError("No XLSX Jinja template found.")

        template = BytesIO(base64.b64decode(file_template))
        doc_obj = self.env[report.model].browse(docids)

        context = {
            "spelled_out": self._spelled_out,
            "formatdate": self._formatdate,
            "company": self.env.company,
            "lang": self._context.get("lang", "id_ID"),
            "sysdate": fields.Datetime.now()
        }

        return self._render_xlsx_jinja_mode(template, doc_obj, data, context, report_name=report.print_report_name)

    def _render_xlsx_jinja_mode(self, template_path, doc_obj, data, context, report_name="report"):
        final_rendered_xlsx_files_buffers = []  # 儲存最終組裝好的 Excel BytesIO 對象

        # 獲取原始模板的所有工作表名稱和索引，以便遍歷
        try:
            original_template_wb_meta = load_workbook(template_path, read_only=True)
            all_template_sheet_info = []
            for idx, sheet_name in enumerate(original_template_wb_meta.sheetnames):
                all_template_sheet_info.append({"name": sheet_name, "index": idx})
            original_template_wb_meta.close()
            print(f"已從模板 '{template_path}' 讀取到 {len(all_template_sheet_info)} 個工作表信息。")

        except Exception as e:
            print(f"錯誤: 無法載入模板 '{template_path}' 以獲取工作表信息: {e}")
            return b"", "error"
        if self.jinja_id and self.jinja_id.tree:
            writer = BookWriter(template_path)
            writer.set_jinja_globals(dir=dir, getattr=getattr)

            # 遍歷原始模板的每個工作表，為當前 doc_obj 渲染對應的內容
            for sheet_info in all_template_sheet_info:
                template_sheet_name = sheet_info["name"]
                template_sheet_index = sheet_info["index"]

                current_payload = {
                    **context,  # 基礎上下文
                    "docs": doc_obj,  # 當前文檔數據
                    "data": data,  # 全局數據
                    "tpl_index": template_sheet_index,  # 指定模板頁面索引
                    "sheet_name": template_sheet_name
                }

                writer.render_sheet(current_payload)

            temp_output_buffer = BytesIO()
            writer.save(temp_output_buffer)  # BookWriter 會將所有渲染後的頁面保存到這裡
            temp_output_buffer.seek(0)
            final_rendered_xlsx_files_buffers.append(temp_output_buffer)
        # 遍歷每個 doc_obj (數據物件)，為每個物件生成一份完整的 Excel 報告
        else:
            for doc_idx, obj in enumerate(doc_obj):

                # 關鍵步驟：為每個 doc_obj 創建一個新的 BookWriter 實例
                # 這樣每個報告都會有一個獨立的輸出 Excel 工作簿
                writer = BookWriter(template_path)
                writer.set_jinja_globals(dir=dir, getattr=getattr)

                # 遍歷原始模板的每個工作表，為當前 doc_obj 渲染對應的內容
                for sheet_info in all_template_sheet_info:
                    template_sheet_name = sheet_info["name"]
                    template_sheet_index = sheet_info["index"]

                    print(f"  正在渲染 doc_obj[{doc_idx}] 的模板頁面 '{template_sheet_name}' (索引: {template_sheet_index})...")

                    # 構建 payload，明確指定：
                    # 1. 'tpl_index': 告訴 get_sheet_resource 選擇原始模板中的哪一頁來渲染。
                    # 2. 'sheet_name': 告訴 get_sheet_name 最終輸出 Excel 中這頁的名稱。
                    current_payload = {
                        **context,  # 基礎上下文
                        "docs": obj,  # 當前文檔數據
                        "data": data,  # 全局數據
                        "tpl_index": template_sheet_index,  # 指定模板頁面索引
                        "sheet_name": template_sheet_name
                    }

                    # 調用 render_sheet，它將會把渲染結果寫入到 writer 內部維護的輸出工作簿中
                    writer.render_sheet(current_payload)

                    # 當當前 doc_obj 的所有模板頁面都渲染完畢後，保存這個完整的 Excel 檔案
                temp_output_buffer = BytesIO()
                writer.save(temp_output_buffer)  # BookWriter 會將所有渲染後的頁面保存到這裡
                temp_output_buffer.seek(0)
                final_rendered_xlsx_files_buffers.append(temp_output_buffer)
                print(f"--- 完成為 doc_obj[{doc_idx}] 生成完整 Excel 檔案。---")

        # 根據生成的 Excel 檔案數量決定返回單個檔案還是 ZIP 壓縮包
        zip_buffer = BytesIO()
        if len(final_rendered_xlsx_files_buffers) == 1:
            return final_rendered_xlsx_files_buffers[0].read(), "xlsx"
        else:
            with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
                for idx, xlsx_file_buffer in enumerate(final_rendered_xlsx_files_buffers):
                    name = safe_eval(report_name, {"object": doc_obj[idx], "time": time})
                    filename = "%s.%s" % (name, "xlsx")
                    zip_file.writestr(filename, xlsx_file_buffer.read())

            zip_buffer.seek(0)
            return zip_buffer.read(), "zip"

    # Render Function
    @staticmethod
    def _formatdate(date_required=fields.Datetime.today(), format="full", lang="id_ID", **kwargs):
        return format_date(date=date_required, format=format, locale=lang, **kwargs)

    @staticmethod
    def _spelled_out(number, lang="id_ID", **kwargs):
        return num2words(number=number, lang=lang, **kwargs)
